import React, { useEffect, useMemo, useRef, useState } from 'react';
import { Select, Spin } from 'antd';
import type { SelectProps } from 'antd';
import debounce from 'lodash/debounce';

interface DebounceSelectProps<ValueType = any>
  extends Omit<SelectProps<ValueType | ValueType[]>, 'options' | 'children'> {
  fetchOptions: (search: string) => Promise<ValueType[]>;
  debounceTimeout?: number;
  selectedValue?: string | number | null | undefined;
};

function DebounceSelect<
  ValueType extends { key?: string; label: React.ReactNode; value: string | number } = any
>({ fetchOptions, debounceTimeout = 800, selectedValue, ...props }: DebounceSelectProps<ValueType>) {
  const [fetching, setFetching] = useState(false);
  const [options, setOptions] = useState<ValueType[]>([]);
  const [value, setValue] = useState<ValueType | ValueType[] | null | undefined>(undefined);
  const fetchRef = useRef(0);

  const debounceFetcher = useMemo(() => {
    const findValue = (options: ValueType[], selectedValue: string | number | null | undefined) => {
      let value: ValueType | null | undefined = undefined;
      if (selectedValue !== null && selectedValue !== undefined) {
        value = options.find(option => option.value === selectedValue) || undefined;
      }
      if (value === null || value === undefined) {
        return value;
      }
      return [value];
    };
    const loadOptions = (value: string, selectedValue: string | number | null | undefined) => {
      fetchRef.current += 1;
      const fetchId = fetchRef.current;
      setOptions([]);
      setFetching(true);
      fetchOptions(value).then((newOptions) => {
        if (fetchId !== fetchRef.current) {
          return;
        }
        setOptions(newOptions);
        setFetching(false);
        setValue(findValue(newOptions, selectedValue));
      });
    };
    return debounce(loadOptions, debounceTimeout);
  }, [fetchOptions, debounceTimeout]);

  useEffect(() => {
    if (fetchRef.current === 0) {
      debounceFetcher('', selectedValue);
    }
  }, [selectedValue, debounceFetcher]);

  return (
    <Select
      mode="multiple"
      maxCount={1}
      labelInValue
      filterOption={false}
      value={value}
      onSearch={value => debounceFetcher(value, selectedValue)}
      onDeselect={() => debounceFetcher('', null)}
      notFoundContent={fetching ? <Spin size="small" /> : null}
      {...props}
      options={options}
      className="debounce-select"
    />
  );
};

export default DebounceSelect;
